package com.alex.seo.service.scheduler;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

@DisallowConcurrentExecution
public class GenericJob implements org.quartz.Job, BeanFactoryAware {

	private static final Logger LOGGER = LoggerFactory.getLogger(GenericJob.class);

	private BeanFactory beanFactory;

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

	@Override
	@SuppressWarnings("unchecked")
	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
		
		Class<? extends Job<?>> jobClass = (Class<? extends Job<?>>) jobDataMap.get(JobConstants.JOB_CLASS);
		JobScheduler jobScheduler = (JobScheduler) jobDataMap.get(JobConstants.JOB_SCHEDULER);
		Object param = jobDataMap.get(JobConstants.JOB_PARAM);
		
		GenericJobContext genericJobContext = SchedulerUtil.parseJobContext(context);
		
		try {
			Job<?> job = (Job<?>) beanFactory.getBean(jobClass);
			execute(job, param);
		} catch (com.alex.seo.service.scheduler.JobExecutionException e) {
			handleException(e.getCause(), context, jobScheduler, genericJobContext);
			return;
		} catch (Throwable e) {
			handleException(e, context, jobScheduler, genericJobContext);
			return;
		}
		
		try {
			Integer nextFireDelay = jobScheduler.scheduleJob(genericJobContext.getTimesTriggered());
			genericJobContext.incTimesTriggered();
			SchedulerUtil.rescheduleJob(context, nextFireDelay, genericJobContext);
		} catch (SchedulerException e) {
			throw new JobExecutionException(e, true);
		}
	}

	private void handleException(Throwable t, JobExecutionContext context, JobScheduler jobScheduler, GenericJobContext genericJobContext) throws JobExecutionException {
		Integer refireDelay = jobScheduler.rescheduleJobOnError(t, genericJobContext.getErrorCount());
		if (refireDelay != null) {
			LOGGER.error(String.format("Will refire job {} in {} seconds.", context.getJobDetail().getKey(), refireDelay / 1000), t);
			try {
				genericJobContext.incErrorCount();
				SchedulerUtil.rescheduleJob(context, refireDelay, genericJobContext);
			} catch (SchedulerException e1) {
				throw new JobExecutionException(e1, true);
			}
		}
	}

	@SuppressWarnings("unchecked")
	private <T> void execute(Job<T> job, Object param) throws Throwable {
		job.execute((T) param);
	}
}
